package quasar;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;

import quasar_framework.Common;
import quasar_framework.Message;
import quasar_framework.MessageType;
import quasar_framework.PublishMessageData;
import il.technion.ewolf.kbr.Key;
import il.technion.ewolf.kbr.KeybasedRouting;
import il.technion.ewolf.kbr.Node;

public class FloodStation extends Station
{
	public FloodStation(int ID, KeybasedRouting kbr)
	{
		super(ID, kbr);
	}
	
	@Override
	public void publish(int messageID, String messageContent)
	{
		Message message = new Message(MessageType.PUBLISH, this.radius, messageID);
		PublishMessageData messageData = new PublishMessageData(super.ID, super.ID, messageContent);
		message.data = messageData;
		message.alreadyReceived.add(super.ID);
		message.ttl = Common.subscriptionTTL;
		message.hops = 0;
		
		for (Node neighbor : this.realNeighbours)
		try
		{
			this.kbr.sendMessage(neighbor, Common.publicationTag, message);
		}

		catch (Exception e)
		{
			System.out.println("Station " + super.ID + " encountered an error while trying to randomly send its publication. Error is " + e.getLocalizedMessage());
		}

		Statistics.addPublication(super.ID);
		synchronized(Statistics.sentPublications)
		{
			Statistics.sentPublications++;
		}
	}
	
	@Override
	private void handlePublishMessage(Node from, Message message)
	{
		try
		{
			PublishMessageData messageData = (PublishMessageData)message.data;
			
			message.ttl--;
			message.hops++;
	
			// If the message is sent to the publisher and has more TTL,
			// It is sent to a random neighbor
			if (super.ID == (messageData.publisherID))
			{
				if (message.ttl > 0)
				{			
					ArrayList<Node> toSend = fetchRandomRecievers(this.realNeighbours, 1, message.alreadyReceived);
					
					if (1 == toSend.size())
					{
						try
						{
							this.kbr.sendMessage(toSend.get(0), Common.publicationTag, message);
						}
					
						catch (Exception e)
						{
							System.out.println("Station " + super.ID + " encountered an error while trying to randomly send a publication. Error is " + e.getLocalizedMessage());
							return;
						}
					}
					else
					{
						outputMessageRoute(message);
					}
				}
				else
				{
					outputMessageRoute(message);
				}
				return;
			}
			
			
			// If the station is a subscriber for this message,
			// He zeroes the TTL, and sends it to alpha neighbors randomly
			do
			{
				if (this.subscriptions.contains(messageData.groupID))
				{
					
					// Just for tracking
					synchronized(this.receivedPublicationMessages)
					{
						if (!this.receivedPublicationMessages.contains(message.ID))
						{
							synchronized(Statistics.deliveredPublishPackets)
							{
								Statistics.deliveredPublishPackets++;
							}
							this.receivedPublicationMessages.add(message.ID);
							System.out.println("Subscriber " + super.ID + " received the publication from " + messageData.publisherID);
						}
						else if (message.alreadyReceived.contains(super.ID))
						{
							break;
						}
						else
						{
							break;
						}
					}
					
					// Adding myself to the already received list, since i'm like a new sender
					if (!message.alreadyReceived.contains(super.ID))
					{
						message.alreadyReceived.add(super.ID);
					}
					
					message.ttl = Common.publicationTTL;
						
					ArrayList<Node> toSend = fetchRandomRecievers(this.realNeighbours, Common.alpha, message.alreadyReceived);
					if (0 < toSend.size())
					{
						for (Node potentialReceiver : toSend)
						{
							try
							{
								this.kbr.sendMessage(potentialReceiver, Common.publicationTag, message);
							}
							catch (Exception e)
							{
								System.out.println("Station " + super.ID + " encountered an error while trying to randomly send a publication. Error is " + e.getLocalizedMessage());
								return;
							}
						}
					}
					else
					{
						outputMessageRoute(message);
					}
	
					return;
				}
			} while(false);
			
			// Checking the routing table entries to see if any level matches
			// the publication request
			if (message.ttl > 0)
			{
				int foundLevel = -1;
				Key neighbourKey = null;
					
				synchronized(this.routingTable)
				{
					for (int i = 0; i < radius ; i++)
					{
						for (Key nodeKey : routingTable.get(i).keySet())
						{
							// Not sending back to the sender
							if (nodeKey.equals(from.getKey()))
							{
								continue;
							}
							
							// It the message is already sent to a node which is achievable by this bloom filter, continue
							
							boolean alreadyReceivedFlag = false;
							for (int alreadyReceivedStation : message.alreadyReceived)
							{
								if (routingTableAux.get(i).get(nodeKey).contains(alreadyReceivedStation))
								{
									alreadyReceivedFlag = true;
									break;
								}
							}
							
							if (alreadyReceivedFlag)
							{
								continue;
							}
								
							if (routingTable.get(i).get(nodeKey).contains(messageData.groupID))
							{
								neighbourKey = nodeKey;
								foundLevel = i;
								break;
							}
						}
					}
				}
					
				// There's no entry on the routing table which requires the
				// publication, so we'll send to a neighbor in random
				if (-1 == foundLevel)
				{
					ArrayList<Node> toSend = fetchRandomRecievers(this.realNeighbours, 1, message.alreadyReceived);
					
					if (1 == toSend.size())
					{

						try
						{
							this.kbr.sendMessage(toSend.get(0), Common.publicationTag, message);
						}
						catch (Exception e)
						{
							System.out.println("Station " + super.ID + " encountered an error while trying to randomly send a publication. Error is " + e.getLocalizedMessage());
							return;
						}
					}
					else
					{
						outputMessageRoute(message);
					}
				}
					
				// Found a routing table entry which requires the publication
				else
				{
					Node nextHop = null;
					for (Node neighbourCandidate : realNeighbours)
					{
						if (neighbourCandidate.getKey().equals(neighbourKey))
						{
							nextHop = neighbourCandidate;
							break;
						}
					}
						
					if (null == nextHop)
					{
						System.out.println("Station " + super.ID + " encountered an error while trying to randomly send a publication. This is not expected at all!");
						return;
					}
						
					else
					{
		 				try
		 				{
		 					this.kbr.sendMessage(nextHop, Common.publicationTag, message);
		 				}
		 				catch (Exception e)
		 				{
		 					System.out.println("Station " + super.ID + " encountered an error while trying to route a publication using the routing table. Error is " + e.getLocalizedMessage());
		 					return;
		 				}
					}
				}
			}
			else
			{
				outputMessageRoute(message);
			}
		}
		catch (Exception e)
		{
			System.out.println("Station " + super.ID + " encountered an error while handling a received publication. Error is " + e.getLocalizedMessage());
			return;
		}
	}
}
